import { Graph, Edge } from "@antv/x6";
import { Node } from "@antv/x6/lib/model/node";
import { PortManager } from "@antv/x6/lib/model/port";

export const LINE_HEIGHT = 24;
export const NODE_WIDTH = 200;
export const sMarkerPath = "line/sourceMarker",
  tMarkerPath = "line/targetMarker",
  plus = "circlePlus",
  path = "path";

const strokeColor = "#726eaa",
  fillColor = strokeColor,
  labelColor = "#ffffff",
  labelFontSize = 12,
  portFillColor = "#EFF4FF",
  keyFontSize = 10,
  rv = 6;

Graph.registerEdge(
  "custom-edge", // 边名称
  {
    // 基类
    inherit: "edge",
    // 属性样式
    attrs: {
      line: {
        stroke: "#5755a1",
      },
    },
    // 默认标签
    defaultLabel: {
      markup: [
        {
          tagName: "rect",
          selector: "body",
        },
        {
          tagName: "text",
          selector: "label",
        },
      ],
      attrs: {
        label: {
          fill: "black",
          fontSize: 14,
          textAnchor: "middle",
          textVerticalAnchor: "middle",
          pointerEvents: "none",
        },
        body: {
          ref: "label",
          fill: "white",
          stroke: "#5755a1",
          strokeWidth: 2,
          rx: 4,
          ry: 4,
          refWidth: "140%",
          refHeight: "140%",
          refX: "-20%",
          refY: "-20%",
        },
      },
      position: {
        distance: 100, // 绝对定位
        options: {
          absoluteDistance: true,
        },
      },
    },
  }
);

Graph.registerPortLayout(
  "erPortPosition",
  (portsPositionArgs) => {
    return portsPositionArgs.map((_, index) => {
      return {
        position: {
          x: 0,
          y: (index + 1) * LINE_HEIGHT + rv * 2,
        },
        angle: 0,
      };
    });
  },
  true
);

Graph.registerNode(
  "er-rect",
  {
    inherit: "rect",
    markup: [
      {
        tagName: "rect",
        selector: "body",
      },
      {
        tagName: "rect",
        selector: "title",
      },
      {
        tagName: "text",
        selector: "label",
      },
    ],
    attrs: {
      body: {
        rx: rv,
        ry: rv,
        strokeWidth: 0,
        stroke: strokeColor,
        fill: portFillColor,
        filter: {
          name: "dropShadow",
          args: {
            dx: rv,
            dy: rv,
            blur: rv,
            opacity: 1,
          },
        },
      },
      title: {
        ref: "body",
        rx: rv,
        ry: rv,
        strokeWidth: 1,
        stroke: strokeColor,
        fill: fillColor,
        refWidth: "100%",
        height: LINE_HEIGHT + rv * 2 - 2,
      },
      label: {
        ref: "body",
        fontWeight: "bold",
        fill: labelColor,
        fontSize: labelFontSize,
        refY: "17",
        textWrap: {
          width: "80%",
          height: LINE_HEIGHT,
          ellipsis: true,
        },
      },
    },
    ports: {
      groups: {
        list: {
          markup: [
            {
              tagName: "g",
              selector: "labelGroup",
              children: [
                {
                  tagName: "rect",
                  selector: "portBody",
                },
                {
                  tagName: "rect",
                  selector: "portName",
                },
                {
                  tagName: "rect",
                  selector: "portType",
                },
                {
                  tagName: "rect",
                  selector: "portMemo",
                },
                {
                  tagName: "text",
                  selector: "portNameLabel",
                },
                {
                  tagName: "text",
                  selector: "portTypeLabel",
                },
                {
                  tagName: "text",
                  selector: "portMemoLabel",
                },
              ],
            },
          ],
          attrs: {
            labelGroup: {
              magnet: true,
            },
            portBody: {
              height: LINE_HEIGHT,
              fill: portFillColor,
            },
            portName: {
              ref: "portBody",
              refWidth: "33.3%",
              height: LINE_HEIGHT,
              refX: "0%",
              fill: portFillColor,
            },
            portType: {
              ref: "portBody",
              refWidth: "33.3%",
              height: LINE_HEIGHT,
              refX: "33.3%",
              fill: portFillColor,
            },
            portMemo: {
              ref: "portBody",
              refWidth: "33.3%",
              height: LINE_HEIGHT,
              refX: "66.6%",
              fill: portFillColor,
            },
            portNameLabel: {
              ref: "portName",
              refX: "5%",
              refY: "33%",
              fontSize: keyFontSize,
              textWrap: {
                width: "100%",
                height: LINE_HEIGHT,
                ellipsis: true,
              },
            },
            portTypeLabel: {
              ref: "portType",
              refX: "5%",
              refY: "33%",
              fontSize: keyFontSize,
              textWrap: {
                width: "100%",
                height: LINE_HEIGHT,
                ellipsis: true,
              },
            },
            portMemoLabel: {
              ref: "portMemo",
              refX: "5%",
              refY: "33%",
              fontSize: keyFontSize,
              textWrap: {
                width: "100%",
                height: LINE_HEIGHT,
                ellipsis: true,
              },
            },
          },
          position: "erPortPosition",
        },
      },
    },
  },
  true
);

export type ErDesign = {
  id: string;
  name: string;
  tables: ErTable[];
  edges: ErEdge[];
};

export type ErTable = {
  id: string;
  name: string;
  label: string;
  memo: string;
  items: ErCol[];
  x: number;
  y: number;
  w: number;
};

export type ErCol = {
  id: string;
  name: string;
  type: string;
  memo: string;
  isIndex: boolean;
  index: string;
  unique: string;
  cs: number[];
};

export type ErEdge = {
  source: { cell: string; port: string };
  target: { cell: string; port: string };
  attrs: {
    line: {
      stroke: string;
      targetMarker: string;
      sourceMarker: string;
    };
  };
};

const mkEdge = (edge: Edge<Edge.Properties>): ErEdge => {
  const erEdge: ErEdge = {
    source: {
      cell: "",
      port: "",
    },
    target: {
      cell: "",
      port: "",
    },
    attrs: {
      line: {
        stroke: edge.attr("line/stroke"),
        targetMarker: edge.attr("line/targetMarker"),
        sourceMarker: edge.attr("line/sourceMarker"),
      },
    },
  };
  Object.assign(erEdge.source, JSON.parse(JSON.stringify(edge.source)));
  Object.assign(erEdge.target, JSON.parse(JSON.stringify(edge.target)));
  return erEdge;
};

export const SetNodeHeightByData = (data: ErTable, node: Node): Node => {
  if (data && node) {
    const height = LINE_HEIGHT * (data.items.length + 1) + rv * 3;
    if (height != node.size().height) node.setSize(node.size().width, height);
  }
  return node;
};

export const PortToNode = (
  data: ErTable,
  item: ErCol
): PortManager.PortMetadata => {
  return {
    id: `${data.id}-${item.id}`,
    group: "list",
    attrs: {
      portBody: {
        width: data.w,
      },
      portNameLabel: {
        text: item.name,
      },
      portTypeLabel: {
        text: item.type,
      },
      portMemoLabel: {
        text: item.memo,
      },
    },
  };
};

export const dataToNode = (data: ErTable): Node.Metadata => {
  if (!data.w) data.w = NODE_WIDTH;
  const node: Node.Metadata = {
    id: data.id,
    shape: "er-rect",
    label: NodeLabel(data.name, data.label),
    width: data.w,
    height: LINE_HEIGHT * (data.items.length + 1) + rv * 3,
    x: data.x,
    y: data.y,
  };
  const ports: Partial<PortManager.Metadata> = {
    items: [],
  };
  node.ports = ports;
  for (let i = 0; i < data.items.length; i++) {
    const item = data.items[i];
    node.ports.items?.push(PortToNode(data, item));
  }
  return node;
};

export const dataToNodes = (info: ErDesign): Node.Metadata[] => {
  const rest: Node.Metadata[] = [];
  info.tables.forEach((data) => {
    const node = dataToNode(data);
    rest.push(node);
  });
  return rest;
};

export const dataToEdge = (info: ErDesign): Edge.Metadata[] => {
  const rest: Edge.Metadata[] = [];
  info.edges.forEach((data) => {
    const edge: Edge.Metadata = {
      shape: "edge",
      source: {
        cell: data.source.cell,
        port: data.source.port,
      },
      target: {
        cell: data.target.cell,
        port: data.target.port,
      },
      attrs: data.attrs,
    };
    rest.push(edge);
  });
  return rest;
};

export const JsonStrToErDesign = (info: string): ErDesign => {
  const erDesign: ErDesign = {
    id: "",
    name: "",
    tables: [],
    edges: [],
  };
  if (info && info.length > 0) {
    Object.assign(erDesign, JSON.parse(info));
  }
  return erDesign;
};

export const UpdateErTablePosition = (
  id: string,
  x: number,
  y: number,
  info: string
): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  erDesign.tables.forEach((t) => {
    if (id == t.id) {
      t.x = x;
      t.y = y;
    }
  });
  return JSON.stringify(erDesign, null, 2);
};

export const UpdateErTableWidth = (
  id: string,
  w: number,
  info: string
): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  erDesign.tables.forEach((t) => {
    if (id == t.id) {
      t.w = w;
    }
  });
  return JSON.stringify(erDesign, null, 2);
};

export const PushEdgeToErDesign = (
  info: string,
  edge: Edge<Edge.Properties>
): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  const erEdge: ErEdge = mkEdge(edge);
  erDesign.edges.push(erEdge);
  return JSON.stringify(erDesign, null, 2);
};

export const PullEdgeFromErDesign = (
  info: string,
  edge: Edge<Edge.Properties>
): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  erDesign.edges = erDesign.edges.filter((e) => {
    return !(
      edge.getSourceCellId() === e.source.cell &&
      edge.getSourcePortId() === e.source.port &&
      edge.getTargetCellId() === e.target.cell &&
      edge.getTargetPortId() === e.target.port
    );
  });
  return JSON.stringify(erDesign, null, 2);
};

export const UpdateEdgeToErDesign = (
  info: string,
  edge: Edge<Edge.Properties>
): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  let index = -1;
  for (let i = 0; i < erDesign.edges.length; i++) {
    const e = erDesign.edges[i];
    if (
      edge.getSourceCellId() === e.source.cell &&
      edge.getSourcePortId() === e.source.port &&
      edge.getTargetCellId() === e.target.cell &&
      edge.getTargetPortId() === e.target.port
    ) {
      index = i;
      break;
    }
  }
  if (index >= 0) {
    erDesign.edges[index] = mkEdge(edge);
  }
  return JSON.stringify(erDesign, null, 2);
};

export const PushTableToErDesign = (info: string, table: ErTable): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  erDesign.tables.push(table);
  return JSON.stringify(erDesign, null, 2);
};

export const PullTableFromErDesign = (info: string, table: ErTable): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  erDesign.tables = erDesign.tables.filter((e) => {
    return table.id != e.id;
  });
  erDesign.edges = erDesign.edges.filter((e) => {
    return !(e.source.cell === table.id || e.target.cell === table.id);
  });
  return JSON.stringify(erDesign, null, 2);
};

const nodePortId = (tbId: string, itemId: string): string => {
  return tbId + "-" + itemId;
};

export const UpdateTableToErDesign = (info: string, table: ErTable): string => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  let index = -1;
  for (let i = 0; i < erDesign.tables.length; i++) {
    if (table.id === erDesign.tables[i].id) {
      index = i;
      break;
    }
  }
  if (index >= 0) {
    erDesign.tables[index] = table;
    for (let i = erDesign.edges.length - 1; -1 < i; i--) {
      const e = erDesign.edges[i];
      let portInfo = "";
      if (e.source.cell === table.id) {
        portInfo = e.source.port;
      } else if (e.target.cell === table.id) {
        portInfo = e.target.port;
      }
      if (portInfo) {
        const tes = table.items.filter((t) => {
          return nodePortId(table.id, t.id) === portInfo;
        });
        if (tes.length === 0) {
          erDesign.edges.splice(i, 1);
        }
      }
    }
  }
  return JSON.stringify(erDesign, null, 2);
};

export const GetMaxTableIdFromErDesign = (info: string): number => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  let maxId = 0;
  erDesign.tables.forEach((e) => {
    const id = Number(e.id);
    if (maxId < id) maxId = id;
  });
  return maxId;
};

export const UpdateNodePortsByErTableInfo = (
  nodeEdit: Node<Node.Properties>,
  tInfo: ErTable
) => {
  const removePortAts: number[] = [];
  for (let i = 0; i < nodeEdit.ports.items.length; i++) {
    const item = nodeEdit.ports.items[i];
    if (item.id && tInfo.items) {
      let hv = false;
      for (let j = 0; j < tInfo.items.length; j++) {
        const e = tInfo.items[j],
          namePath = "attrs/portNameLabel/text",
          typePath = "attrs/portTypeLabel/text",
          memoPath = "attrs/portMemoLabel/text",
          name = nodeEdit?.portProp(item.id, namePath),
          type = nodeEdit?.portProp(item.id, typePath),
          memo = nodeEdit?.portProp(item.id, memoPath),
          id = nodePortId(nodeEdit.id, e.id);
        if (id === item.id) {
          if (name && name != e.name) {
            nodeEdit?.portProp(item.id, namePath, e.name);
          }
          if (type && type != e.type) {
            nodeEdit?.portProp(item.id, typePath, e.type);
          }
          if (memo && memo != e.memo) {
            nodeEdit?.portProp(item.id, memoPath, e.memo);
          }
          hv = true;
          break;
        }
      }
      if (!hv) removePortAts.push(i);
    }
  }
  // 清理字段
  removePortAts.forEach((id) => {
    nodeEdit?.removePortAt(id);
  });
  // 添加字段
  for (let j = 0; j < tInfo.items.length; j++) {
    const e = tInfo.items[j];
    if (e.id && nodeEdit.ports.items) {
      let hv = false;
      for (let i = 0; i < nodeEdit.ports.items.length; i++) {
        const item = nodeEdit.ports.items[i],
          id = nodePortId(nodeEdit.id, e.id);
        if (id === item.id) {
          hv = true;
          break;
        }
      }
      if (!hv) {
        nodeEdit.addPort(PortToNode(tInfo, e));
      }
    }
  }
};

export const ItemCs = (
  tbId: string,
  itemId: string,
  edges: ErEdge[]
): number[] => {
  const cs: number[] = [];
  for (let i = 0; i < edges.length; i++) {
    const edge = edges[i];
    if (edge.source.cell === tbId) {
      if (nodePortId(tbId, itemId) === edge.source.port) {
        if (path === edge.attrs.line.sourceMarker) {
          cs.push(1);
        } else {
          cs.push(2);
        }
      }
    }
    if (edge.target.cell === tbId) {
      if (nodePortId(tbId, itemId) === edge.target.port) {
        if (path === edge.attrs.line.targetMarker) {
          cs.push(1);
        } else {
          cs.push(2);
        }
      }
    }
  }
  return cs;
};

export const NodeLabel = (name: string, label: string): string => {
  return name + "（" + label + "）";
};

export const GetNameFromEdge = (
  info: string,
  edge: Edge
): {
  source: {
    tbName: string;
    tbLabel: string;
    itemName: string;
    relation: number;
  };
  target: {
    tbName: string;
    tbLabel: string;
    itemName: string;
    relation: number;
  };
} => {
  const erDesign: ErDesign = JsonStrToErDesign(info);
  const res = {
    source: {
      tbName: "",
      tbLabel: "",
      itemName: "",
      relation: 0,
    },
    target: {
      tbName: "",
      tbLabel: "",
      itemName: "",
      relation: 0,
    },
  };
  for (let i = 0; i < erDesign.tables.length; i++) {
    const tb = erDesign.tables[i];
    if (tb.id === edge.getSourceCellId()) {
      res.source.tbName = tb.name;
      res.source.tbLabel = tb.label;
      const itemNames = tb.items
        .filter((e) => {
          return nodePortId(tb.id, e.id) === edge.getSourcePortId();
        })
        .map((e) => {
          return e.name;
        });
      res.source.itemName = itemNames.join(", ");
    }
    if (tb.id === edge.getTargetCellId()) {
      res.target.tbName = tb.name;
      res.target.tbLabel = tb.label;
      const itemNames = tb.items
        .filter((e) => {
          return nodePortId(tb.id, e.id) === edge.getTargetPortId();
        })
        .map((e) => {
          return e.name;
        });
      res.target.itemName = itemNames.join(", ");
    }
  }
  return res;
};
